home *** CD-ROM | disk | FTP | other *** search
- /* SCSIFindNextDevice.c */
- /*
- * SCSIFindNextDevice.c
- * Copyright © 1992-94 Apple Computer Inc. All Rights Reserved.
- *
- * Find all SCSI devices. The alogrithm first asks the SCSI Manager for the
- * number of buses, then loops through each bus for each device and LUN.
- * old SCSI Manager. This is made complex by the flexible SCSI Manager 4.3
- * architecture: it is possible for the asynchronous SCSI Manager to only
- * be available on a third-party bus interface, for example. Because of this,
- * we must always scan the bus using the original SCSI Manager even if the
- * asynchronous manager is present.
- */
- #include <Errors.h>
- #include <Events.h>
- #include <Memory.h>
- #include <OSUtils.h>
- #include <Traps.h>
- #include <Types.h>
- #include "SCSIFindDevices.h"
- #ifndef FALSE
- #define FALSE 0
- #define TRUE 1
- #endif
-
- /*
- * All functions use the same formal parameter to access the "global" record.
- */
- #define REC (*scsiFindDevicesPtr)
-
- /*
- * These are the states that control the overall process.
- */
- enum {
- kStateInitialize = 0,
- kStateNextBus,
- kStateNextTarget,
- kStateNextLUN,
- kStateCheckForHardWired,
- kStateNextHardWiredTarget,
- kStateNextHardWiredLUN,
- kStateLastStateWithoutATrailingCommaBecauseWeCareAboutYou
- };
-
- /*
- * These are the commands that may be sent to the device. Request Sense is only
- * sent by the original SCSI Manager.
- */
- #define kScsiCmdInquiry 0x12
- #define kScsiCmdRequestSense 0x03
-
- /*
- * These are the device types that SCSI knows about.
- */
- enum {
- kScsiDevTypeDirect = 0,
- kScsiDevTypeSequential,
- kScsiDevTypePrinter,
- kScsiDevTypeProcessor,
- kScsiDevTypeWorm, /* Write-once, read multiple */
- kScsiDevTypeCDROM,
- kScsiDevTypeScanner,
- kScsiDevTypeOptical,
- kScsiDevTypeChanger,
- kScsiDevTypeComm,
- kScsiDevTypeGraphicArts0A,
- kScsiDevTypeGraphicArts0B,
- kScsiDevTypeFirstReserved, /* Start of reserved sequence */
- kScsiDevTypeUnknownOrMissing = 0x1F,
- kScsiDevTypeMask = 0x1F
- };
- /*
- * These are device type modifiers. We need them to distinguish between "unknown"
- * and "missing" devices.
- */
- enum {
- kScsiDevTypeQualifierConnected = 0x00, /* Exists and is connected */
- kScsiDevTypeQualifierNotConnected = 0x20, /* Logical unit exists */
- kScsiDevTypeQualifierReserved = 0x40,
- kScsiDevTypeQualifierMissing = 0x60, /* No such logical unit */
- kScsiDevTypeQualifierVendorSpecific = 0x80, /* Other bits are unspecified */
- kScsiDevTypeQualifierMask = 0xE0
- };
- #define kScsiDevTypeMissing \
- (kScsiDevTypeUnknownOrMissing | kScsiDevTypeQualifierMissing)
- /*
- * This is the data that is returned after a GetExtendedStatus request. The
- * errorCode gives a general indication of the error, which may be qualified by
- * the additionalSenseCode and additionalSenseQualifier fields. These may be
- * device (vendor) specific values, however. The info[] field contains additional
- * information. For a media error, it contains the failing logical block number
- * (most-significant byte first).
- */
- struct SCSI_Sense_Data { /* Request Sense result */
- unsigned char errorCode; /* 0 Class code, valid lbn */
- unsigned char segmentNumber; /* 1 Segment number */
- unsigned char senseKey; /* 2 Sense key and flags */
- unsigned char info[4];
- unsigned char additionalSenseLength;
- unsigned char reservedForCopy[4];
- unsigned char additionalSenseCode;
- unsigned char additionalSenseQualifier;
- unsigned char fruCode; /* Field replacable unit code */
- unsigned char senseKeySpecific[2];
- unsigned char additional[101];
- };
- typedef struct SCSI_Sense_Data SCSI_Sense_Data;
- /*
- * The high-bit of errorCode signals whether there is a logical
- * block. The low value signals whether there is a valid sense
- */
- #define kScsiSenseHasLBN 0x80 /* Logical block number set */
- #define kScsiSenseInfoValid 0x70 /* Is sense key valid? */
- #define kScsiSenseInfoMask 0x70 /* Mask for sense info */
- /*
- * These bits may be set in the sense key
- */
- #define kScsiSenseKeyMask 0x0F
- #define kScsiSenseILI 0x20 /* Illegal logical Length */
- #define kScsiSenseEOM 0x40 /* End of media */
- #define kScsiSenseFileMark 0x80 /* End of file mark */
-
- /*
- * SCSI sense codes. (Returned after request sense).
- */
- #define kScsiSenseNone 0x00 /* No error */
- #define kScsiSenseRecoveredErr 0x01 /* Warning */
- #define kScsiSenseNotReady 0x02 /* Device not ready */
- #define kScsiSenseMediumErr 0x03 /* Device medium error */
- #define kScsiSenseHardwareErr 0x04 /* Device hardware error */
- #define kScsiSenseIllegalReq 0x05 /* Illegal request for dev. */
- #define kScsiSenseUnitAtn 0x06 /* Unit attention (not err) */
- #define kScsiSenseDataProtect 0x07 /* Data protection */
- #define kScsiSenseBlankCheck 0x08 /* Tape-specific error */
- #define kScsiSenseVendorSpecific 0x09 /* Vendor-specific error */
- #define kScsiSenseCopyAborted 0x0a /* Copy request cancelled */
- #define kScsiSenseAbortedCmd 0x0b /* Initiator aborted cmd. */
- #define kScsiSenseEqual 0x0c /* Comparison equal */
- #define kScsiSenseVolumeOverflow 0x0d /* Write past end mark */
- #define kScsiSenseMiscompare 0x0e /* Comparison failed */
- #define kScsiSenseCurrentErr 0x70
- #define kScsiSenseDeferredErr 0x71
-
-
- /*
- * SCSI command status (from status phase)
- */
- #define kScsiStatusGood 0x00 /* Normal completion */
- #define kScsiStatusCheckCondition 0x02 /* Need GetExtendedStatus */
- #define kScsiStatusConditionMet 0x04 /* For Compare Command? */
- #define kScsiStatusBusy 0x08 /* Device busy (self-test?) */
- #define kScsiStatusIntermediate 0x10 /* Intermediate status */
- #define kScsiStatusResConflict 0x18 /* Reservation conflict */
- #define kScsiStatusQueueFull 0x28 /* Target can't do command */
- #define kScsiStatusReservedMask 0x3e /* Vendor specific? */
-
- /*
- * This is the maximum number of times we try to grab the SCSI Bus
- */
- #define kMaxSCSIRetries 40 /* 10 seconds, 4 times/sec */
- /*
- * This test is TRUE if the SCSI bus status indicates "busy" (which is the case
- * if either the BSY or SEL bit is set).
- */
- #ifndef kScsiStatBSY
- #define kScsiStatBSY (1 << 6)
- #endif
- #ifndef kScsiStatSEL
- #define kScsiStatSEL (1 << 1)
- #endif
- #define ScsiBusBusy() ((SCSIStat() & (kScsiStatBSY | kScsiStatSEL)) != 0)
-
- /*
- * These private routines do all the work.
- */
- static void CheckForAsyncSCSI(
- register SCSIFindDevicesPtr scsiFindDevicesPtr
- );
- static void GetHighHostBusAdaptor(
- register SCSIFindDevicesPtr scsiFindDevicesPtr
- );
- static OSErr SetupForNextSCSIBus(
- register SCSIFindDevicesPtr scsiFindDevicesPtr
- );
- static Boolean IsRegisteredAsynchDevice(
- register SCSIFindDevicesPtr scsiFindDevicesPtr
- );
- static Boolean CheckForDevice(
- register SCSIFindDevicesPtr scsiFindDevicesPtr,
- Boolean useAsyncManager
- );
- static OSErr OriginalSCSI(
- unsigned short targetID,
- Ptr command,
- Ptr resultData,
- unsigned long resultSize,
- long *actualTransferSize
- );
- static void ClearMemory(
- void *recordPtr,
- unsigned long recordLength
- );
- #define CLEAR(record) ClearMemory(&record, sizeof record);
-
-
- /*
- * This is the function that does all the work. Each time it is called, it finds
- * the next device (next logical unit, next target, next bus). The overall design
- * uses a state machine that continues within the function until one of three
- * things happen: it finds a device, it reaches the end of the device sequence,
- * or it gets an error.
- */
- OSErr
- SCSIFindNextDevice(
- register SCSIFindDevicesPtr scsiFindDevicesPtr
- )
- {
- OSErr status;
- Boolean doNextState;
-
- if (REC.deviceID.bus == 0xFF)
- REC.state = kStateInitialize;
- /*
- * This is the overall state machine that processes application calls.
- * Code in this section only organizes the lower-level routines. Many of
- * the tests within the state switch change state. In all cases, they set
- * doNextState TRUE and exit the switch. This, eventually, continues at
- * the while statement which re-runs the state switch. A state-machine
- * organization is not necessarily the best (and goto's would be slightly
- * more efficient), but looping through a common switch statement
- * simplified debugging.
- */
- doNextState = TRUE;
- while (doNextState) {
- doNextState = FALSE;
- switch (REC.state) {
- case kStateInitialize:
- /*
- * Initialization: check for the presence of the asynchronous
- * SCSI Manager and get the last host bus. Then start with the
- * first bus.
- */
- REC.scsiExecIOPB = NULL;
- REC.execIOPBSize = 0;
- CheckForAsyncSCSI(scsiFindDevicesPtr);
- GetHighHostBusAdaptor(scsiFindDevicesPtr);
- REC.state = kStateNextBus;
- doNextState = TRUE;
- break;
- case kStateNextBus:
- /*
- * Look for the first/next bus. If we have a bus, we do some
- * bus-specific initializations including, primarily, creating
- * the SCSI parameter block.
- */
- if (REC.deviceID.bus == 0xFF)
- REC.deviceID.bus = 0; /* Do the first bus */
- else {
- ++REC.deviceID.bus; /* Do the next bus */
- }
- if (REC.deviceID.bus <= REC.lastHostBus) {
- /*
- * We have another bus to test. Make a SCSI parameter block
- * that is properly sized for this bus.
- */
- status = SetupForNextSCSIBus(scsiFindDevicesPtr);
- if (status == noErr) {
- /*
- * This bus exists: check its targets.
- */
- REC.deviceID.targetID = 0xFF;
- REC.state = kStateNextTarget;
- doNextState = TRUE;
- }
- else if (status == eofErr) { /* eofErr is private */
- /*
- * This bus does not exist. There may be gaps in the bus
- * sequence if a third-party SCSI adaptor installs its
- * own, private, SCSI Manager 4.3 on a machine that does
- * not otherwise support asynchronous SCSI. Just continue
- * with the next bus until we reach REC.lastHostBus.
- */
- REC.state = kStateNextBus;
- doNextState = TRUE;
- }
- else {
- /*
- * Oops: this is a serious error. Since doNextState is
- * FALSE, we will exit the while loop and return to the
- * caller with a serious error.
- */
- }
- }
- else {
- /*
- * We've examined all of the buses accessable through SCSI
- * Manager 4.3. Check for third-party devices that are only
- * accessable through the original SCSI Manager (because they
- * patch the SCSI Manager traps).
- */
- REC.deviceID.targetID = 0xFF;
- REC.state = kStateCheckForHardWired;
- doNextState = TRUE;
- }
- break;
- case kStateNextTarget:
- /*
- * Look for the next device on this bus. If we run off the end,
- * cycle back through the switch to look for the next bus.
- */
- if (REC.deviceID.targetID == 0xFF)
- REC.deviceID.targetID = 0; /* Do the first target */
- else {
- ++REC.deviceID.targetID; /* Do the next target */
- }
- /*
- * REC.initiatorID is set on a bus-by-bus basis. We cannot assume
- * that it is always equal to seven. Skip over the initiator.
- * REC.maxTargetID is normally seven; but it's provided to us
- * by the SCSI Manager, so we'll use that value.
- */
- if (REC.deviceID.targetID == REC.initiatorID)
- ++REC.deviceID.targetID;
- if (REC.deviceID.targetID > REC.maxTargetID) {
- /*
- * We've done all targets for this bus. Go on to the next bus.
- */
- REC.state = kStateNextBus;
- }
- else {
- /*
- * New target: do the first logical unit for this bus/target.
- */
- REC.deviceID.LUN = 0xFF;
- REC.state = kStateNextLUN;
- }
- doNextState = TRUE;
- break;
- case kStateNextLUN:
- /*
- * We have a host bus and target ID. Cycle through the logical
- * units for this target ID. We will always look at LUN zero.
- * When we reach the end, the switch will take us to the next
- * target ID.
- */
- if (REC.deviceID.LUN == 0xFF)
- REC.deviceID.LUN = 0; /* Do the first LUN */
- else {
- ++REC.deviceID.LUN; /* Do the next LUN */
- }
- if (REC.deviceID.LUN > REC.maxBusLUN) {
- REC.state = kStateNextTarget;
- doNextState = TRUE;
- }
- else {
- /*
- * Look for this LUN. Failures look for the next target.
- */
- if (CheckForDevice(scsiFindDevicesPtr, REC.useAsynchSCSI))
- status = noErr; /* This one exists */
- else {
- /*
- * This target/LUN was not found. Go on to the next
- * SCSI bus ID. Note that we presume that there are no
- * gaps in the LUN sequence.
- */
- REC.state = kStateNextTarget;
- doNextState = TRUE;
- }
- }
- break;
- case kStateCheckForHardWired:
- /*
- * We have found all devices that can be accessed through the
- * asynchronous SCSI Manager. Some third-party hardware interfaces
- * do not use the asynchronous SCSI Manager, but patch original
- * SCSI traps. If the asynchronous SCSI Manager is not available,
- * we don't have to continue here (the above code found all
- * devices. In this segment, we must hard-wire the initiator ID
- * to seven, as there is no supported way to determine it from the
- * SCSI Manager or operating system.
- */
- if (REC.isAsyncSCSIPresent == FALSE)
- status = eofErr; /* All done, thank you */
- else {
- REC.deviceID.bus = 0; /* Set the hard-wired data */
- REC.initiatorID = 7;
- REC.maxTargetID = 7;
- REC.state = kStateNextHardWiredTarget;
- REC.deviceID.targetID = 0xFF;
- doNextState = TRUE;
- }
- break;
- case kStateNextHardWiredTarget:
- /*
- * Look at the next target on the built-in bus.
- */
- if (REC.deviceID.targetID == 0xFF)
- REC.deviceID.targetID = 0;
- else {
- ++REC.deviceID.targetID;
- }
- if (REC.deviceID.targetID == REC.initiatorID)
- ++REC.deviceID.targetID;
- if (REC.deviceID.targetID >= REC.maxTargetID)
- status = eofErr; /* All done, thank you */
- else {
- REC.deviceID.LUN = 0xFF;
- REC.state = kStateNextHardWiredLUN;
- doNextState = TRUE;
- }
- break;
- case kStateNextHardWiredLUN:
- /*
- * Look for the next LUN on the built-in bus.
- */
- if (REC.deviceID.LUN == 0xFF) {
- REC.maxBusLUN = REC.maxLUN;
- REC.deviceID.LUN = 0; /* Do the first LUN */
- }
- else {
- ++REC.deviceID.LUN; /* Do the next LUN */
- }
- if (REC.deviceID.LUN > REC.maxBusLUN) {
- REC.state = kStateNextHardWiredTarget;
- doNextState = TRUE;
- }
- else {
- /*
- * Look for this LUN. failures look for the next target.
- * IsRegisteredAsynchDevice is TRUE if this device did not
- * register with the asynchronous SCSI Manager.
- */
- if (IsRegisteredAsynchDevice(scsiFindDevicesPtr)) {
- /*
- * Since we know about this device, it would have been
- * found by the first pass through the asynchronous
- * SCSI Manager. So, we don't need to look at it here.
- */
- REC.state = kStateNextHardWiredLUN;
- doNextState = TRUE;
- }
- else if (CheckForDevice(scsiFindDevicesPtr, FALSE)) {
- /*
- * This device was not registered, but it does exist
- * if we check using the original SCSI Manager. Probably
- * because someone patched the SCSI Manager traps.
- * Return this to the caller.
- */
- status = noErr; /* This one exists */
- }
- else {
- /*
- * This target/LUN was not found. Go on to the next
- * SCSI bus ID. Note that we presume that there are no
- * gaps in the LUN sequence.
- */
- REC.state = kStateNextHardWiredTarget;
- doNextState = TRUE;
- }
- }
- break;
- default: /* Illegal state */
- status = abortErr; /* Can't happen */
- break;
- } /* State switch */
- } /* While doNextState */
- if (status != noErr) {
- /*
- * We've done everything that we can. Re-initialize and release
- * our saved parameter block.
- */
- REC.deviceID.bus = 0xFF; /* Force reinitialize */
- if (REC.scsiExecIOPB != NULL) {
- DisposePtr((Ptr) REC.scsiExecIOPB);
- REC.scsiExecIOPB = NULL;
- }
- }
- return (status);
- }
-
- /*
- * Check whether the Asynchronous SCSI trap is available.
- * Set REC.isAsyncSCSI appropriately.
- */
- static void
- CheckForAsyncSCSI(
- register SCSIFindDevicesPtr scsiFindDevicesPtr
- )
- {
- TrapType trapType;
- short theTrap;
-
- /*
- * TrapAvailable (see Inside Mac VI 3-8)
- */
- #define NumToolboxTraps() ( \
- (NGetTrapAddress(_InitGraf, ToolTrap) \
- == NGetTrapAddress(0xAA6E, ToolTrap)) \
- ? 0x200 : 0x400 \
- )
- #define GetTrapType(theTrap) ( \
- (((theTrap) & 0x0800) != 0) ? ToolTrap : OSTrap \
- )
- theTrap = _SCSIAtomic;
- trapType = GetTrapType(theTrap);
- if (trapType == ToolTrap) {
- theTrap &= 0x07FF;
- if (theTrap >= NumToolboxTraps())
- theTrap = _Unimplemented;
- }
- REC.isAsyncSCSIPresent = (
- NGetTrapAddress(theTrap, trapType)
- != NGetTrapAddress(_Unimplemented, ToolTrap)
- );
- #undef NumToolboxTraps
- #undef GetTrapType
- }
-
- /*
- * If we have the asynchronous SCSI Manager, find out how many buses are present
- * on this system. If we're limited to the original SCSI Manager, force a "single
- * bus" scan, since the function that actually calls the SCSI Manager ignores the
- * bus if the asynchronous manager is not present.
- */
- static void
- GetHighHostBusAdaptor(
- register SCSIFindDevicesPtr scsiFindDevicesPtr
- )
- {
- OSErr status;
- SCSIBusInquiryPB busInquiryPB;
- #define PB (busInquiryPB)
-
- if (REC.isAsyncSCSIPresent == FALSE)
- REC.lastHostBus = 0;
- else {
- CLEAR(PB);
- PB.scsiPBLength = sizeof PB;
- PB.scsiFunctionCode = SCSIBusInquiry;
- PB.scsiDevice.bus = 0xFF;
- status = SCSIAction((SCSI_PB *) &PB);
- REC.lastHostBus = PB.scsiHiBusID;
- }
- #undef PB
- }
-
- /*
- * Start to check a new SCSI bus. If we find a bus, allocate (or re-allocate)
- * the SCSIExecIO command block. Note that it is possible to have buses with
- * no devices, and gaps in the bus sequence. For example, in a Macintosh with
- * two buses (such as the Quadra 950 or PowerMac 8100), you can have a bus
- * with no devices.Also, if you install a third-party bus adaptor that supports
- * the asynchronous SCSI Manager on a machine with two buses, it would be
- * assigned bus 2 (with buses 0 and 1 referencing the internal system buses).
- * In this case, a system could have no devices on bus 0 or 1.
- */
- static OSErr
- SetupForNextSCSIBus(
- register SCSIFindDevicesPtr scsiFindDevicesPtr
- )
- {
- OSErr status;
- SCSIBusInquiryPB busInquiryPB;
- #define PB (busInquiryPB)
-
- /*
- * If we don't support asynchronous SCSI, nothing happens here.
- */
- if (REC.isAsyncSCSIPresent == FALSE) {
- REC.useAsynchSCSI = FALSE;
- REC.initiatorID = 7;
- REC.maxTargetID = 7;
- REC.maxBusLUN = REC.maxLUN;
- status = noErr;
- }
- else {
- CLEAR(PB);
- PB.scsiPBLength = sizeof PB;
- PB.scsiFunctionCode = SCSIBusInquiry;
- PB.scsiDevice.bus = REC.deviceID.bus; /* Other values are zero */
- status = SCSIAction((SCSI_PB *) &PB);
- if (status == noErr) {
- REC.useAsynchSCSI = TRUE; /* Asynch works on this bus */
- REC.initiatorID = PB.scsiInitiatorID;
- REC.maxTargetID = PB.scsiMaxTarget;
- /*
- * If we are running on a Quadra 840-AV with a CD300, the
- * Macintosh will hang if it tries to access LUN 1. We check for
- * this problem in two ways: by examining a global maxLUN flag,
- * and by checking whether the bug was fixed, either by running
- * on later hardware or by installing a System Update.
- */
- REC.enableATN =
- (busInquiryPB.scsiWeirdStuff & scsiTargetDrivenSDTRSafe) != 0;
- REC.maxBusLUN = (REC.enableATN) ? REC.maxLUN : 0;
- /*
- * Allocate a parameter block for this request using the size that
- * was returned in the busInquiry parameter block.
- */
- if (REC.execIOPBSize != PB.scsiIOpbSize) {
- if (REC.scsiExecIOPB != NULL) {
- DisposePtr((Ptr) REC.scsiExecIOPB);
- REC.scsiExecIOPB = NULL;
- }
- REC.scsiExecIOPB =
- (SCSIExecIOPB *) NewPtrClear(PB.scsiIOpbSize);
- if (REC.scsiExecIOPB == NULL)
- status = MemError();
- else {
- REC.execIOPBSize = PB.scsiIOpbSize;
- status = noErr;
- }
- }
- }
- else if (PB.scsiDevice.bus == 0) {
- /*
- * If bus zero is not registered, it must be accessed via the
- * original API. Set the initiatorID to the default 7 - there is
- * no supported mechanism for determining the actual id (it's
- * hidden inside the parameter RAM, but cannot be set by a
- * published mechanism).
- */
- REC.useAsynchSCSI = FALSE;
- REC.initiatorID = 7;
- REC.maxTargetID = 7;
- REC.maxBusLUN = REC.maxLUN;
- status = noErr;
- }
- else {
- /*
- * This is a problem: this bus cannot be accessed unless it has
- * been registered. For example, the second bus of a Quadra
- * 950 cannot be accessed if the SCSI Manager extension is not
- * installed and a third-party asynchronous SCSI Manager card
- * is installed as bus 2. Return a private error to skip this bus.
- */
- status = eofErr;
- }
- }
- return (status);
- #undef PB
- }
-
- /*
- * This is called if the asynchronous SCSI Manager is present to sweep up any
- * third party devices that did not register with the asynchronous SCSI Manager.
- */
- static Boolean
- IsRegisteredAsynchDevice(
- register SCSIFindDevicesPtr scsiFindDevicesPtr
- )
- {
- OSErr status;
- SCSIGetVirtualIDInfoPB scsiGetVirtualIDInfo;
-
- CLEAR(scsiGetVirtualIDInfo);
- scsiGetVirtualIDInfo.scsiPBLength = sizeof scsiGetVirtualIDInfo;
- scsiGetVirtualIDInfo.scsiOldCallID = REC.deviceID.targetID;
- status = SCSIAction((SCSI_PB *) &scsiGetVirtualIDInfo);
- return (status == noErr);
- }
-
- /*
- * This is the only function that sends SCSI commands to a device. It will send a
- * Device Inquiry and, if Check Condition is returned, issue Request Sense.
- */
- static Boolean
- CheckForDevice(
- register SCSIFindDevicesPtr scsiFindDevicesPtr,
- Boolean useAsyncManager
- )
- {
-
- OSErr status;
- OSErr requestSenseStatus;
- SCSI_Sense_Data senseData;
- #define SENSE (senseData)
- Boolean result;
- /*
- * For the old SCSI Manager only.
- */
- unsigned char command[6];
- long tempLong;
-
- if (useAsyncManager) {
- #define PB (*REC.scsiExecIOPB)
- /*
- * Setup the parameter block for the user's request.
- */
- PB.scsiPBLength = REC.execIOPBSize;
- PB.scsiFunctionCode = SCSIExecIO;
- PB.scsiTimeout = 1000;
- PB.scsiDevice = REC.deviceID;
- PB.scsiCDBLength = 6;
- PB.scsiCDB.cdbBytes[0] = kScsiCmdInquiry;
- PB.scsiCDB.cdbBytes[4] = sizeof REC.inquiry;
- /*
- * Stuff the LUN into the command for SCSI-1 devices.
- */
- /* PB.scsiCDB.cdbBytes[1] &= ~0xE0; -- already zero */
- PB.scsiCDB.cdbBytes[1] |= (REC.deviceID.LUN & 0x03) << 5;
- /*
- * Specify the transfer direction, if any, and setup the other SCSI
- * operation flags. scsiSIMQNoFreeze prevents the SCSI Manager from
- * blocking further operation if an error is detected.
- */
- PB.scsiFlags = scsiTransferPolled;
- PB.scsiDataPtr = (unsigned char *) &REC.inquiry;
- PB.scsiDataLength = sizeof REC.inquiry;
- PB.scsiDataType = scsiDataBuffer;
- PB.scsiSensePtr = (unsigned char *) &senseData;
- PB.scsiSenseLength = sizeof senseData;
- PB.scsiFlags = scsiSIMQNoFreeze | scsiDirectionIn | scsiDontDisconnect;
- if (REC.enableATN == FALSE)
- PB.scsiIOFlags |= scsiDisableSelectWAtn;
- status = SCSIAction((SCSI_PB *) &PB);
- if (status == noErr)
- status = PB.scsiResult;
- /*
- * Note: scsiDataRunError is issued if our transfer request was larger
- * or smaller than the actual transfer length. We need to examine the
- * actual transfer sizes to see how to handle this error. This is
- * not necessarily complete or correct. The intent here is to supress
- * the transfer length error when executing Device Inquiry or other
- * administrative commands with variable-length result blocks.
- */
- REC.actualInquirySize = PB.scsiDataLength - PB.scsiDataResidual;
- if (status == scsiDataRunError /* Over/underrun error */
- && REC.actualInquirySize > 0) /* And its a short read */
- status = noErr; /* If so, ignore error */
- /*
- * If the device issued Check Condition and the SCSI Manager was able
- * to retrieve a Request Sense datum, change the error to our private
- * "Check Condition" status.
- */
- if (status == scsiNonZeroStatus
- && (PB.scsiResultFlags & scsiAutosenseValid) != 0) {
- status = statusErr;
- requestSenseStatus = noErr;
- }
- #undef PB
- }
- else {
- CLEAR(command);
- command[0] = kScsiCmdInquiry;
- command[4] = sizeof REC.inquiry;
- /*
- * Stuff the LUN into the command.
- */
- /* command[1] &= ~0xE0; -- already zero */
- command[1] |= (REC.deviceID.LUN & 0x03) << 5;
- status = OriginalSCSI(
- REC.deviceID.targetID,
- (Ptr) &command,
- (Ptr) &REC.inquiry,
- sizeof REC.inquiry,
- &REC.actualInquirySize
- );
- if (status == statusErr) {
- /*
- * Check condition
- */
- CLEAR(command);
- command[0] = kScsiCmdRequestSense;
- command[4] = sizeof senseData;
- /*
- * Stuff the LUN into the command.
- */
- /* command[1] &= ~0xE0; -- already zero */
- command[1] |= (REC.deviceID.LUN & 0x03) << 5;
- requestSenseStatus = OriginalSCSI(
- REC.deviceID.targetID,
- (Ptr) &command,
- (Ptr) &senseData,
- sizeof senseData,
- &tempLong
- );
- if (status == noErr)
- status = statusErr;
- }
- }
- /*
- * Look at the result.
- */
- switch (status) {
- case noErr:
- if (REC.inquiry.devType == kScsiDevTypeMissing)
- result = FALSE;
- else {
- result = TRUE; /* Normal successful return */
- }
- break;
- case statusErr:
- /*
- * The target returned Check Condition. We need to look at the sense
- * data, if any, to distinguish between an "offline" but present device,
- * and a non-existant logical unit. Note: some drives return Check
- * Condition, and "no sense error" if we try to access an incorrect
- * logical unit. This might reasonably be remapped as "illegal request.
- */
- if (requestSenseStatus != noErr
- || (SENSE.errorCode & kScsiSenseInfoMask) != kScsiSenseInfoValid)
- result = FALSE;
- else {
- switch (SENSE.senseKey & kScsiSenseKeyMask) {
- case kScsiSenseIllegalReq:
- result = FALSE;
- break;
- default:
- /*
- * Wierd: some drives seem to set the End of Media bit
- * in the sense key if an invalid LUN is selected.
- */
- if ((SENSE.senseKey & kScsiSenseEOM) != 0)
- result = FALSE;
- else {
- result = TRUE;
- }
- break;
- }
- }
- break;
- default: /* Other errors == no such device */
- result = FALSE;
- break;
- }
- return (result);
- }
-
- /*
- * This is a very limited wrapper for the original SCSI Manager that can handle
- * Device Inquiry and Request Sense (only).
- */
- static OSErr
- OriginalSCSI(
- unsigned short targetID,
- Ptr command,
- Ptr resultData,
- unsigned long resultSize,
- long *actualTransferSize
- )
- {
- OSErr status;
- OSErr completionStatus;
- short totalTries; /* Get/Select retries */
- short getTries; /* Get retries */
- short iCount; /* Bus free counter */
- unsigned long watchdog; /* Timeout after this */
- SCSIInstr tib[4];
- short messageByte;
- short statusByte;
-
- *actualTransferSize = 0;
- tib[0].scOpcode = scInc;
- tib[0].scParam1 = (unsigned long) resultData;
- tib[0].scParam2 = 1;
- tib[1].scOpcode = scAdd;
- tib[1].scParam1 = (unsigned long) actualTransferSize;
- tib[1].scParam2 = 1;
- tib[2].scOpcode = scLoop;
- tib[2].scParam1 = (-2 * sizeof (SCSIInstr));
- tib[2].scParam2 = resultSize;
- tib[3].scOpcode = scStop;
- tib[3].scParam1 = 0;
- tib[3].scParam2 = 0;
- /*
- * Arbitrate for the scsi bus. This will fail if some other device is
- * accessing the bus at this time (which is unlikely).
- *
- *** Do not set breakpoints or call any functions that may require device
- *** I/O (such as display code that accesses font resources between
- *** SCSIGet and SCSIComplete,
- *
- */
- for (totalTries = 0; totalTries < kMaxSCSIRetries; totalTries++) {
- for (getTries = 0; getTries < 4; getTries++) {
- /*
- * Wait for the bus to go free.
- */
- watchdog = TickCount() + 300; /* 5 second timeout */
- while (ScsiBusBusy()) {
- if (TickCount() > watchdog) {
- status = scArbNBErr;
- goto exit;
- }
- }
- /*
- * The bus is free, try to grab it
- */
- for (iCount = 0; iCount < 4; iCount++) {
- if ((status = SCSIGet()) == noErr)
- break;
- }
- if (status == noErr)
- break; /* Success: we have the bus */
- /*
- * The bus became busy again. Try to wait for it to go free.
- */
- for (iCount = 0; iCount < 100 && ScsiBusBusy(); iCount++)
- ;
- } /* The getTries loop */
- if (status != noErr) {
- /*
- * The SCSI Manager thinks the bus is not busy and not selected,
- * but "someone" has set its internal semaphore that signals
- * that the SCSI Manager itself is busy. The application will have
- * to handle this problem. (We tried getTries * 4 times).
- */
- goto exit;
- }
- /*
- * We now own the SCSI bus. Try to select the device.
- */
- if ((status = SCSISelect(targetID)) != noErr)
- goto exit;
- /*
- * From this point on, we must exit through SCSIComplete() even if an
- * error is detected. Send a command to the selected device. There are
- * several failure modes, including an illegal command (such as a
- * write to a read-only device). If the command failed because of
- * "device busy", we will try it again.
- */
- status = SCSICmd((Ptr) command, 6);
- if (status == noErr)
- status = SCSIRead((Ptr) tib);
- /*
- * SCSIComplete "runs" the bus-phase algorithm until the bitter end,
- * returning the status and command-completion message bytes..
- */
- completionStatus = SCSIComplete(&statusByte, &messageByte, 60L);
- /*
- * If we have an error here, return as the "final" status.
- *
- */
- if (completionStatus != noErr)
- status = completionStatus;
- else {
- /*
- * ScsiComplete is happy. If the device is busy, Pause for 1/4
- * second and try again.
- */
- if (statusByte == kScsiStatusBusy) {
- watchdog = TickCount() + 15;
- while (TickCount() < watchdog)
- ;
- continue; /* Do next totalTries attempt */
- }
- }
- /*
- * This is the normal exit (success) or final failure exit.
- */
- break;
- } /* totalTries loop */
- exit: /*
- * Ignore phase errors if the buffer was large enough
- */
- if (status == scPhaseErr && *actualTransferSize <= resultSize)
- status = noErr;
- /*
- * Return statusErr if the device returns Check Condition:
- * Also, there is a bug in the combination of System 7.0.1 and the 53C96
- * that may cause the real SCSI Status Byte to be in the Message byte.
- */
- if (statusByte == kScsiStatusGood
- && messageByte == kScsiStatusCheckCondition)
- statusByte = kScsiStatusCheckCondition;
- if (status == noErr && statusByte == kScsiStatusCheckCondition)
- status = statusErr;
- return (status);
- }
-
-
- static void
- ClearMemory(
- void *recordPtr,
- register unsigned long recordLength
- )
- {
- register char *ptr;
-
- for (ptr = (char *) recordPtr; recordLength > 0; --recordLength)
- *ptr++ = 0;
- }
-
-